﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using EDMDesigner.Business;
using EDMDesigner.Business.AssociationBase;
using EDMDesigner.Business.CSDL;
using EDMDesigner.Business.CSDL.Association;
using EDMDesigner.Business.CSDL.Property;
using EDMDesigner.Business.CSDL.Property.Complex;
using EDMDesigner.Business.CSDL.Property.Navigation;
using EDMDesigner.Business.CSDL.Property.Scalar;
using EDMDesigner.Business.CSDL.Type;
using EDMDesigner.Business.CSDL.Type.Complex;
using EDMDesigner.Business.CSDL.Util;
using EDMDesigner.Business.FunctionBase;
using EDMDesigner.Business.MSL.Condition;
using EDMDesigner.Business.MSL.CUDFunction;
using EDMDesigner.Business.MSL.EntityType;
using EDMDesigner.Business.SSDL;
using EDMDesigner.Business.SSDL.EntityType;
using EDMDesigner.Business.SSDL.Function;
using EDMDesigner.Business.SSDL.Property;
using EDMDesigner.Business.MSL.CUDFunction.Association;

namespace EDMDesigner.EdmxRW
{
    public static class Reader
    {
        private const string EDMX_NS = "http://schemas.microsoft.com/ado/2007/06/edmx";
        private const string SSDL_NS = "http://schemas.microsoft.com/ado/2006/04/edm/ssdl";
        private const string SSDL_STORE_NS = "http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator";
        private const string CSDL_NS = "http://schemas.microsoft.com/ado/2006/04/edm";
        private const string CSDL_VIBILITY_NS = "http://schemas.microsoft.com/ado/2006/04/codegeneration";
        private const string MSL_NS = "urn:schemas-microsoft-com:windows:storage:mapping:CS";

        public static EDM Read(string edmxPath, Action<XElement> readMoreAction)
        {
            var edmx = XElement.Load(edmxPath);
            var edmxRuntime = edmx.Element(XName.Get("Runtime", EDMX_NS));

            var ssdlContainer = GetSSDLContainer(edmxRuntime);
            var csdlContainer = GetCSDLContainer(edmxRuntime);
            var edmxDesigner = edmx.Element(XName.Get("Designer", EDMX_NS));
            var value = new EDM { SSDLContainer = ssdlContainer, CSDLContainer = IntegrateMSLInCSDLContainer(csdlContainer, ssdlContainer, edmxRuntime), DesignerProperties = edmxDesigner.Element(XName.Get("Connection", EDMX_NS)).Element(XName.Get("DesignerInfoPropertySet", EDMX_NS)).Elements(XName.Get("DesignerProperty", EDMX_NS)).Select(e => new DesignerProperty { Name = e.Attribute("Name").Value, Value = e.Attribute("Value").Value }), EDMXDesignerDesignerProperties = edmxDesigner.Element(XName.Get("Options", EDMX_NS)).Element(XName.Get("DesignerInfoPropertySet", EDMX_NS)).Elements(XName.Get("DesignerProperty", EDMX_NS)).Select(e => new DesignerProperty { Name = e.Attribute("Name").Value, Value = e.Attribute("Value").Value }), EDMXDesignerDiagrams = edmxDesigner.Element(XName.Get("Diagrams", EDMX_NS)).Elements(XName.Get("Diagram", EDMX_NS))};
            readMoreAction(edmx);
            return value;
        }

        private static SSDLContainer GetSSDLContainer(XElement edmxRuntime)
        {
            var schemaElement = edmxRuntime.Element(XName.Get("StorageModels", EDMX_NS)).Element(XName.Get("Schema", SSDL_NS));
            var entityContainerElement = schemaElement.Element(XName.Get("EntityContainer", SSDL_NS));

            var value = new SSDLContainer { Namespace = schemaElement.Attribute("Namespace").Value, Name = entityContainerElement.Attribute("Name").Value };
            SetStringValueFromAttribute(schemaElement, "Provider", provider => value.Provider = provider);
            SetStringValueFromAttribute(schemaElement, "ProviderManifestToken", provider => value.ProviderManifestToken = provider);

            #region EntitySets
            foreach (var entitySetElement in entityContainerElement.Elements(XName.Get("EntitySet", SSDL_NS)))
            {
                var entityType = new EDMDesigner.Business.SSDL.EntityType.EntityType { EntitySetName = entitySetElement.Attribute("Name").Value };
                var entityTypeName = GetName(entitySetElement.Attribute("EntityType").Value);
                entityType.Name = entityTypeName;
                SetEnumValueFromAttribute<StoreType>(entitySetElement, "Type", SSDL_STORE_NS, storeType => entityType.StoreType = storeType);
                SetStringValueFromAttribute(entitySetElement, "Schema", schema => entityType.Schema = schema);
                SetStringValueFromAttribute(entitySetElement, "Name", SSDL_STORE_NS, storeName => entityType.StoreName = storeName);
                SetStringValueFromAttribute(entitySetElement, "Schema", SSDL_STORE_NS, storeSchema => entityType.StoreSchema = storeSchema);
                SetStringValueFromAttribute(entitySetElement, "Table", table => entityType.Table = table);
                SetStringValueFromElement(entitySetElement, "DefiningQuery", SSDL_NS, query => entityType.DefiningQuery = query);

                #region Properties
                var entityTypeElement = schemaElement.Elements(XName.Get("EntityType", SSDL_NS)).First(etElement => etElement.Attribute("Name").Value == entityTypeName);
                foreach (var propertyElement in entityTypeElement.Elements(XName.Get("Property", SSDL_NS)))
                {
                    var name = propertyElement.Attribute("Name").Value;
                    var property = new Property(entityType) { Name = name, Type = propertyElement.Attribute("Type").Value, IsKey = entityTypeElement.Element(XName.Get("Key", SSDL_NS)).Elements(XName.Get("PropertyRef", SSDL_NS)).Where(pr => pr.Attribute("Name").Value == name).Any() };
                    SetBoolValueFromAttribute(propertyElement, "Nullable", nullable => property.Nullable = nullable);
                    SetIntValueFromAttribute(propertyElement, "MaxLength", maxLength => property.MaxLength = maxLength);
                    SetBoolValueFromAttribute(propertyElement, "FixedLength", fixedLength => property.FixedLength = fixedLength);
                    SetIntValueFromAttribute(propertyElement, "Precision", precision => property.Precision = precision);
                    SetIntValueFromAttribute(propertyElement, "Scale", scale => property.Scale = scale);
                    SetStringValueFromAttribute(propertyElement, "Collation", collation => property.Collation = collation);
                    SetStringValueFromAttribute(propertyElement, "DefaultValue", defaultValue => property.DefaultValue = defaultValue);
                    SetBoolValueFromAttribute(propertyElement, "Unicode", unicode => property.Unicode = unicode);
                    SetEnumValueFromAttribute<StoreGeneratedPattern>(propertyElement, "StoreGeneratedPattern", storeGeneratedPattern => property.StoreGeneratedPattern = storeGeneratedPattern);
                    SetStringValueFromAttribute(propertyElement, "Name", SSDL_STORE_NS, storeName => property.StoreName = storeName);
                    SetStringValueFromAttribute(propertyElement, "Schema", SSDL_STORE_NS, storeSchema => property.StoreSchema = storeSchema);
                    SetStringValueFromAttribute(propertyElement, "Type", SSDL_STORE_NS, storeType => property.StoreType = storeType);
                    entityType.Properties.Add(property);
                }
                #endregion Properties

                value.EntityTypes.Add(entityType);
            }
            #endregion EntitySets

            #region AssociationSets
            foreach (var associationSetElement in entityContainerElement.Elements(XName.Get("AssociationSet", SSDL_NS)))
            {
                var association = new EDMDesigner.Business.SSDL.Association.Association { AssociationSetName = associationSetElement.Attribute("Name").Value };
                var associationName = GetName(associationSetElement.Attribute("Association").Value);
                association.Name = associationName;

                #region Roles
                var associationElement = schemaElement.Elements(XName.Get("Association", SSDL_NS)).First(aElement => aElement.Attribute("Name").Value == associationName);
                bool isPrincipal = true;
                foreach (var roleElement in associationSetElement.Elements(XName.Get("End", SSDL_NS)))
                {
                    var roleName = roleElement.Attribute("Role").Value;
                    var role = new EDMDesigner.Business.SSDL.Association.Role { Name = roleName, Type = value.EntityTypes.GetByName(roleElement.Attribute("EntitySet").Value) };
                    SetCardinalityValueFromAttribute(associationElement.Elements(XName.Get("End", SSDL_NS)).First(are => are.Attribute("Role").Value == roleName), cardinality => role.Cardinality = cardinality);
                    var referentialConstraintElement = associationElement.Element(XName.Get("ReferentialConstraint", SSDL_NS));
                    if (referentialConstraintElement != null)
                    {
                        var principalElement = referentialConstraintElement.Element(XName.Get("Principal", SSDL_NS));
                        if (principalElement.Attribute("Role").Value == role.Name)
                        {
                            isPrincipal = true;
                            association.PrincipalRole = role;
                            role.Properties = role.Type.Properties.Where(p => principalElement.Elements(XName.Get("PropertyRef", SSDL_NS)).Select(e => e.Attribute("Name").Value).Contains(p.Name)).ToList();
                        }
                        else
                        {
                            isPrincipal = false;
                            association.DependantRole = role;
                            role.Properties = role.Type.Properties.Where(p => referentialConstraintElement.Element(XName.Get("Dependent", SSDL_NS)).Elements(XName.Get("PropertyRef", SSDL_NS)).Select(e => e.Attribute("Name").Value).Contains(p.Name)).ToList();
                        }
                    }
                    if (isPrincipal)
                    {
                        association.Role1 = role;
                        isPrincipal = false;
                    }
                    else
                        association.Role2 = role;
                }
                #endregion Roles

                value.AssociationSets.Add(association);
            }
            #endregion AssociationSets

            #region Functions
            foreach (var functionElement in schemaElement.Elements(XName.Get("Function", SSDL_NS)))
            {
                var function = new EDMDesigner.Business.SSDL.Function.Function { Name = functionElement.Attribute("Name").Value };
                SetBoolValueFromAttribute(functionElement, "Aggregate", aggregate => function.Aggregate = aggregate);
                SetBoolValueFromAttribute(functionElement, "BuiltIn", builtIn => function.BuiltIn = builtIn);
                SetBoolValueFromAttribute(functionElement, "IsComposable", isComposable => function.IsComposable = isComposable);
                SetBoolValueFromAttribute(functionElement, "NiladicFunction", niladicFunction => function.NiladicFunction = niladicFunction);
                SetStringValueFromAttribute(functionElement, "Schema", schema => function.Schema = schema);
                SetStringValueFromAttribute(functionElement, "Name", SSDL_STORE_NS, name => function.StoreName = name);
                SetStringValueFromAttribute(functionElement, "Schema", SSDL_STORE_NS, schema => function.StoreSchema = schema);
                SetStringValueFromAttribute(functionElement, "Type", SSDL_STORE_NS, type => function.StoreType = type);
                SetStringValueFromAttribute(functionElement, "FunctionName", SSDL_STORE_NS, functionName => function.StoreFunctionName = functionName);
                SetEnumValueFromAttribute <ParameterTypeSemantics>(functionElement, "ParameterTypeSemantics", parameterTypeSemantics => function.ParameterTypeSemantics = parameterTypeSemantics);

                #region Parameters
                foreach (var parameterElement in functionElement.Elements(XName.Get("Parameter", SSDL_NS)))
                {
                    var parameter = new EDMDesigner.Business.SSDL.Function.FunctionParameter { Name = parameterElement.Attribute("Name").Value, Type = parameterElement.Attribute("Type").Value };
                    SetEnumValueFromAttribute<ParameterMode>(parameterElement, "Mode", mode => parameter.Mode = mode);
                    SetIntValueFromAttribute(parameterElement, "MaxLength", maxLength => parameter.MaxLength = maxLength);
                    SetIntValueFromAttribute(parameterElement, "Precision", precision => parameter.Precision = precision);
                    SetIntValueFromAttribute(parameterElement, "Scale", scale => parameter.Scale = scale);
                    SetStringValueFromAttribute(parameterElement, "Name", SSDL_STORE_NS, name => parameter.StoreName = name);
                    SetStringValueFromAttribute(parameterElement, "Schema", SSDL_STORE_NS, schema => parameter.StoreSchema = schema);
                    SetStringValueFromAttribute(parameterElement, "Type", SSDL_STORE_NS, type => parameter.StoreType = type);

                    function.Parameters.Add(parameter);
                }
                #endregion Parameters

                value.Functions.Add(function);
            }
            #endregion Functions

            return value;
        }

        private static CSDLContainer GetCSDLContainer(XElement edmxRuntime)
        {
            var schemaElement = edmxRuntime.Element(XName.Get("ConceptualModels", EDMX_NS)).Element(XName.Get("Schema", CSDL_NS));
            var entityContainerElement = schemaElement.Element(XName.Get("EntityContainer", CSDL_NS));

            var value = new CSDLContainer { Namespace = schemaElement.Attribute("Namespace").Value, Alias = schemaElement.Attribute("Alias").Value, Name = entityContainerElement.Attribute("Name").Value };

            #region EntityTypes
            while (true)
            {
                var typesEnumerator = (from ete in schemaElement.Elements(XName.Get("EntityType", CSDL_NS))
                                       let eteName = ete.Attribute("Name").Value
                                       where !value.EntityTypes.Any(et => et.Name == eteName)
                                       let baseTypeAttribute = ete.Attribute("BaseType")
                                       let baseType = baseTypeAttribute == null ? null : value.EntityTypes.GetByName(GetName(baseTypeAttribute.Value))
                                       where baseTypeAttribute == null || baseType != null
                                       select new { EntityTypeElement = ete, Name = eteName, BaseType = baseType }).GetEnumerator();
                if (!typesEnumerator.MoveNext())
                    break;
                do
                {
                    var current = typesEnumerator.Current;
                    value.EntityTypes.Add(ReadCSDLEntityType(schemaElement, entityContainerElement, current.EntityTypeElement, value, current.Name, current.BaseType));
                } while (typesEnumerator.MoveNext());
            }
            #endregion EntityTypes

            #region Associations
            foreach (var association in value.AssociationsCreated)
            {
                association.AssociationSetName = entityContainerElement.Elements(XName.Get("AssociationSet", CSDL_NS)).First(ae => GetName(ae.Attribute("Association").Value) == association.Name).Attribute("Name").Value;
                if (association.PropertyEnd2.EntityType == null)
                {
                    var entityTypeName = schemaElement.Elements(XName.Get("Association", CSDL_NS)).First(ae => ae.Attribute("Name").Value == association.Name).Elements(XName.Get("End", CSDL_NS)).First(er => er.Attribute("Role").Value == association.PropertyEnd2Role).Attribute("Type").Value;
                    int dotIndex = entityTypeName.IndexOf(".");
                    if (dotIndex != -1)
                        entityTypeName = entityTypeName.Substring(dotIndex + 1);
                    var entityType = value.EntityTypes.First(et => et.Name == entityTypeName);;
                    entityType.NavigationProperties.Add(association.PropertyEnd2);
                }
            }
            #endregion Associations

            #region ComplexTypes
            foreach (var complexTypeElement in schemaElement.Elements(XName.Get("ComplexType", CSDL_NS)))
            {
                var complexType = new ComplexType { Name = complexTypeElement.Attribute("Name").Value };
                ReadCSDLType(schemaElement, complexTypeElement, value, complexType);
                value.ComplexTypes.Add(complexType);
            }
            #endregion ComplexTypes

            #region Functions
            foreach (var functionElement in entityContainerElement.Elements(XName.Get("FunctionImport", CSDL_NS)))
            {
                var function = new EDMDesigner.Business.CSDL.Function.Function { Name = functionElement.Attribute("Name").Value };
                var returnTypeAttribute = functionElement.Attribute("ReturnType");
                if (returnTypeAttribute != null)
                {
                    var returnTypeValue = returnTypeAttribute.Value;
                    returnTypeValue = returnTypeValue.Remove(returnTypeValue.IndexOf(")")).Substring(returnTypeValue.IndexOf("(") + 1);
                    function.ScalarReturnType = GetScalarPropertyTypeFromAttribute(returnTypeValue);
                    if (function.ScalarReturnType == null)
                        function.EntityType = value.EntityTypes.GetByName(GetName(returnTypeValue));
                }
                SetVisibilityValueFromAttribute(functionElement, "methodAccess", visibility => function.Visibility = visibility);

                #region Function parameters
                foreach (var parameterElement in functionElement.Elements(XName.Get("Parameter", CSDL_NS)))
                {
                    var parameter = new EDMDesigner.Business.CSDL.Function.FunctionParameter { Name = parameterElement.Attribute("Name").Value, Type = GetScalarPropertyTypeFromAttribute(parameterElement).Value };
                    SetEnumValueFromAttribute<ParameterMode>(parameterElement, "Mode", mode => parameter.Mode = mode);
                    SetIntValueFromAttribute(parameterElement, "Precision", precision => parameter.Precision = precision);
                    SetIntValueFromAttribute(parameterElement, "Scale", scale => parameter.Scale = scale);
                    SetIntValueFromAttribute(parameterElement, "MaxLength", maxLength => parameter.MaxLength = maxLength);
                    function.Parameters.Add(parameter);
                }
                #endregion Function parameters

                value.Functions.Add(function);
            }
            #endregion Functions

            return value;
        }
        private static EDMDesigner.Business.CSDL.Type.Entity.EntityType ReadCSDLEntityType(XElement schemaElement, XElement entityContainerElement, XElement entityTypeElement, CSDLContainer container, string typeName, EDMDesigner.Business.CSDL.Type.Entity.EntityType baseType)
        {
            var entityType = new EDMDesigner.Business.CSDL.Type.Entity.EntityType { Name = typeName, BaseType = baseType };
            SetBoolValueFromAttribute(entityTypeElement, "Abstract", isAbstract => entityType.Abstract = isAbstract);
            var entitySetElement = entityContainerElement.Elements(XName.Get("EntitySet", CSDL_NS)).Where(ese => GetName(ese.Attribute("EntityType").Value) == entityType.Name).FirstOrDefault();
            if (entitySetElement != null)
            {
                entityType.EntitySetName = entitySetElement.Attribute("Name").Value;
                SetVisibilityValueFromAttribute(entitySetElement, "GetterAccess", getterAccess => entityType.EntitySetVisibility = getterAccess);
            }
            ReadCSDLType(schemaElement, entityTypeElement, container, (TypeBase)entityType);
            return entityType;
        }
        private static void ReadCSDLType(XElement schemaElement, XElement entityTypeElement, CSDLContainer container, TypeBase baseType)
        {
            if (baseType.Name == null)
                baseType.Name = entityTypeElement.Attribute("Name").Value;
            SetVisibilityValueFromAttribute(entityTypeElement, "TypeAccess", typeAccess => baseType.Visibility = typeAccess);

            foreach (var propertyElement in entityTypeElement.Elements(XName.Get("Property", CSDL_NS)))
            {
                var name = propertyElement.Attribute("Name").Value;
                var keyElement = entityTypeElement.Element(XName.Get("Key", CSDL_NS));
                var propertyType = GetScalarPropertyTypeFromAttribute(propertyElement);
                PropertyBase property;
                if (propertyType == null)
                {
                    property = new ComplexProperty(GetName(propertyElement.Attribute("Type").Value)) { Name = name };
                    baseType.ComplexProperties.Add((ComplexProperty)property);
                }
                else
                {
                    property = new ScalarProperty() { Name = name, IsKey = keyElement != null && keyElement.Elements(XName.Get("PropertyRef", CSDL_NS)).Where(pr => pr.Attribute("Name").Value == name).Any(), Type = propertyType.Value };
                    var scalarProp = (ScalarProperty)property;
                    SetBoolValueFromAttribute(propertyElement, "Nullable", nullable => scalarProp.Nullable = nullable);
                    SetVisibilityValueFromAttribute(propertyElement, "SetterAccess", setterAccess => scalarProp.SetVisibility = setterAccess);
                    SetIntValueFromAttribute(propertyElement, "MaxLength", maxLength => scalarProp.MaxLength = maxLength);
                    SetBoolValueFromAttribute(propertyElement, "Unicode", unicode => scalarProp.Unicode = unicode);
                    SetBoolValueFromAttribute(propertyElement, "FixedLength", fixedLength => scalarProp.FixedLength = fixedLength);
                    SetIntValueFromAttribute(propertyElement, "Precision", precision => scalarProp.Precision = precision);
                    SetIntValueFromAttribute(propertyElement, "Scale", scale => scalarProp.Scale = scale);
                    SetStringValueFromAttribute(propertyElement, "ConcurrencyMode", concurrencyMode => scalarProp.ConcurrencyMode = ConcurrencyMode.None);
                    SetStringValueFromAttribute(propertyElement, "DefaultValue", defaultValue => scalarProp.DefaultValue = defaultValue);
                    SetStringValueFromAttribute(propertyElement, "Collation", collation => scalarProp.Collation = collation);
                    baseType.ScalarProperties.Add(scalarProp);
                }
                SetVisibilityValueFromAttribute(propertyElement, "GetterAccess", getterAccess => property.GetVisibility = getterAccess);
            }
            var entityType = baseType as EDMDesigner.Business.CSDL.Type.Entity.EntityType;
            if (entityType != null)
            {
                foreach (var navigationPropertyElement in entityTypeElement.Elements(XName.Get("NavigationProperty", CSDL_NS)))
                {
                    var navigationPropertyname = navigationPropertyElement.Attribute("Name").Value;
                    var associationName = GetName(navigationPropertyElement.Attribute("Relationship").Value);
                    var associationElement = schemaElement.Elements(XName.Get("Association", CSDL_NS)).First(ae => ae.Attribute("Name").Value == associationName);
                    Association association = container.AssociationsCreated.GetByName(associationName);
                    bool associationExisting = association != null;
                    if (!associationExisting)
                    {
                        association = new Association { Name = associationName };
                        container.AssociationsCreated.Add(association);
                    }
                    var navigationProperty = new NavigationProperty(association) { Name = navigationPropertyname };
                    var roleName = navigationPropertyElement.Attribute("FromRole").Value;
                    SetCardinalityValueFromAttribute(associationElement.Elements(XName.Get("End", CSDL_NS)).First(ee => ee.Attribute("Role").Value == roleName), cardinality => navigationProperty.Cardinality = cardinality);
                    SetVisibilityValueFromAttribute(navigationPropertyElement, "GetterAccess", visibility => navigationProperty.GetVisibility = visibility);
                    SetVisibilityValueFromAttribute(navigationPropertyElement, "SetterAccess", visibility => navigationProperty.SetVisibility = visibility);
                    if (associationExisting)
                    {
                        association.PropertyEnd2 = navigationProperty;
                        association.PropertyEnd2Role = roleName;
                    }
                    else
                    {
                        association.PropertyEnd1 = navigationProperty;
                        association.PropertyEnd1Role = roleName;
                        string toRoleName = navigationPropertyElement.Attribute("ToRole").Value;
                        NavigationProperty fakeNavigationProperty = new NavigationProperty(association) { Name = roleName, Generate = false };
                        SetCardinalityValueFromAttribute(associationElement.Elements(XName.Get("End", CSDL_NS)).First(ee => ee.Attribute("Role").Value == toRoleName), cardinality => fakeNavigationProperty.Cardinality = cardinality);
                        association.PropertyEnd2 = fakeNavigationProperty;
                        association.PropertyEnd2Role = toRoleName;
                    }
                    var referentialConstraintElement = associationElement.Element(XName.Get("ReferentialConstraint", CSDL_NS));
                    if (referentialConstraintElement != null)
                    {
                        var referentialConstraintRoleElement = referentialConstraintElement.Elements().First(rce => rce.Attribute("Role").Value == roleName);
                        var scalarProperties = referentialConstraintRoleElement.Elements(XName.Get("PropertyRef", CSDL_NS)).Select(e => entityType.AllScalarProperties.First(sp => sp.Name == e.Attribute("Name").Value));
                        switch (referentialConstraintRoleElement.Name.LocalName)
                        {
                            case "Principal":
                                association.PrincipalRole = roleName;
                                association.PrincipalProperties = scalarProperties;
                                break;
                            case "Dependent":
                                association.DependentRole = roleName;
                                association.DependentProperties = scalarProperties;
                                break;
                            default:
                                throw new NotImplementedException();
                        }
                    }
                    entityType.NavigationProperties.Add(navigationProperty);
                }
            }
        }

        private static CSDLContainer IntegrateMSLInCSDLContainer(CSDLContainer csdlContainer, SSDLContainer ssdlContainer, XElement edmxRuntime)
        {
            var entityContainerMappingElement = edmxRuntime.Element(XName.Get("Mappings", EDMX_NS)).Element(XName.Get("Mapping", MSL_NS)).Element(XName.Get("EntityContainerMapping", MSL_NS));

            #region EntityTypes
            foreach (var entityTypeMappingElement in entityContainerMappingElement.Elements(XName.Get("EntitySetMapping", MSL_NS)).SelectMany(entitySetElement => entitySetElement.Elements(XName.Get("EntityTypeMapping", MSL_NS))))
            {
                var typeName = entityTypeMappingElement.Attribute("TypeName").Value;
                if (typeName.IndexOf("(") != -1)
                    typeName = typeName.Remove(typeName.IndexOf(")")).Substring(typeName.IndexOf("(") + 1);
                var entityType = csdlContainer.EntityTypes.GetByName(GetName(typeName));
                entityType.Mapping.BeginInit();
                var mapping = entityType.Mapping;
                foreach (var mappingFragmentElement in entityTypeMappingElement.Elements(XName.Get("MappingFragment", MSL_NS)))
                {
                    var table = ssdlContainer.EntityTypes.GetByName(mappingFragmentElement.Attribute("StoreEntitySet").Value);
                    foreach (var scalarPropertyElement in mappingFragmentElement.Elements(XName.Get("ScalarProperty", MSL_NS)))
                    {
                        var scalarProperty = entityType.AllScalarProperties.GetByName(scalarPropertyElement.Attribute("Name").Value);
                        var column = table.Properties.GetByName(scalarPropertyElement.Attribute("ColumnName").Value);
                        entityType.Mapping[scalarProperty, table] = column;
                    }

                    MappComplexProperties(mappingFragmentElement, mapping, entityType, table);

                    #region Conditions
                    foreach (var conditionElement in mappingFragmentElement.Elements(XName.Get("Condition", MSL_NS)))
                    {
                        var columnNameAttribute = conditionElement.Attribute("ColumnName");
                        ConditionMapping condition;
                        if (columnNameAttribute == null)
                            condition = new PropertyConditionMapping { CSDLProperty = entityType.ScalarProperties.GetByName(conditionElement.Attribute("Name").Value), Table = table };
                        else
                            condition = new ColumnConditionMapping { Column = table.Properties.GetByName(columnNameAttribute.Value) };
                        var valueAttribute = conditionElement.Attribute("Value");
                        if (valueAttribute == null)
                        {
                            if (conditionElement.Attribute("IsNull").Value == "false")
                                condition.Operator = ConditionOperator.IsNotNull;
                            else
                                condition.Operator = ConditionOperator.IsNull;
                        }
                        else
                        {
                            condition.Operator = ConditionOperator.Equals;
                            condition.Value = valueAttribute.Value;
                        }
                        mapping.ConditionsMapping.Add(condition);
                    }
                    #endregion Conditions
                }
                mapping.Init = true;
                #region CUD Functions
                var modificationFunctionMappingElement = entityTypeMappingElement.Element(XName.Get("ModificationFunctionMapping", MSL_NS));
                if (modificationFunctionMappingElement != null)
                {
                    var insertFunctionMappingElement = modificationFunctionMappingElement.Element(XName.Get("InsertFunction", MSL_NS));
                    if (insertFunctionMappingElement != null)
                        mapping.InsertFunctionMapping = SetCUDFunctionMapping(ssdlContainer, entityType, insertFunctionMappingElement);
                    var updateFunctionMappingElement = modificationFunctionMappingElement.Element(XName.Get("UpdateFunction", MSL_NS));
                    if (updateFunctionMappingElement != null)
                        mapping.UpdateFunctionMapping = SetCUDFunctionMapping(ssdlContainer, entityType, updateFunctionMappingElement);
                    var deleteFunctionMappingElement = modificationFunctionMappingElement.Element(XName.Get("DeleteFunction", MSL_NS));
                    if (deleteFunctionMappingElement != null)
                        mapping.DeleteFunctionMapping = SetCUDFunctionMapping(ssdlContainer, entityType, deleteFunctionMappingElement);
                }
                #endregion CUD Functions

                entityType.Mapping.EndInit();
            }
            #endregion EntityTypes

            #region Associations
            foreach (var associationSetMappingElement in entityContainerMappingElement.Elements(XName.Get("AssociationSetMapping", MSL_NS)))
            {
                var csdlAssociation = csdlContainer.AssociationsCreated.GetByName(GetName(associationSetMappingElement.Attribute("TypeName").Value));
                csdlAssociation.Mapping.BeginInit();
                var table = ssdlContainer.EntityTypes.GetByName(associationSetMappingElement.Attribute("StoreEntitySet").Value);
                foreach (var endPropertyElement in associationSetMappingElement.Elements(XName.Get("EndProperty", MSL_NS)))
                {
                    NavigationProperty navigationProperty;
                    if (csdlAssociation.PropertyEnd1Role == endPropertyElement.Attribute("Name").Value)
                        navigationProperty = csdlAssociation.PropertyEnd1;
                    else
                        navigationProperty = csdlAssociation.PropertyEnd2;
                    var navigationPropertyMapping = navigationProperty.Mapping;
                    foreach (var scalarPropertyElement in endPropertyElement.Elements(XName.Get("ScalarProperty", MSL_NS)))
                    {
                        var scalarProperty = navigationProperty.EntityType.AllScalarProperties.GetByName(scalarPropertyElement.Attribute("Name").Value);
                        var column = table.Properties.GetByName(scalarPropertyElement.Attribute("ColumnName").Value);
                        navigationPropertyMapping[scalarProperty] = column;
                    }
                }
                foreach (var conditionElement in associationSetMappingElement.Elements(XName.Get("Condition", MSL_NS)))
                {
                    var columnNameAttribute = conditionElement.Attribute("ColumnName");
                    ColumnConditionMapping condition = new ColumnConditionMapping { Column = table.Properties.GetByName(columnNameAttribute.Value) };;
                    var valueAttribute = conditionElement.Attribute("Value");
                    if (valueAttribute == null)
                    {
                        if (conditionElement.Attribute("IsNull").Value == "false")
                            condition.Operator = ConditionOperator.IsNotNull;
                        else
                            condition.Operator = ConditionOperator.IsNull;
                    }
                    else
                    {
                        condition.Operator = ConditionOperator.Equals;
                        condition.Value = valueAttribute.Value;
                    }
                    csdlAssociation.Mapping.ConditionsMapping.Add(condition);
                }
                csdlAssociation.Mapping.SSDLTableMapped = table;
                csdlAssociation.Mapping.EndInit();
            }
            #endregion Associations

            #region Functions
            foreach (var functionMappingElement in entityContainerMappingElement.Elements(XName.Get("FunctionImportMapping", MSL_NS)))
                csdlContainer.Functions.GetByName(functionMappingElement.Attribute("FunctionImportName").Value).SSDLFunction = ssdlContainer.Functions.GetByName(GetName(functionMappingElement.Attribute("FunctionName").Value));
            #endregion Funtions

            return csdlContainer;
        }

        private static CUDFunctionMapping SetCUDFunctionMapping(SSDLContainer ssdlContainer, EDMDesigner.Business.CSDL.Type.Entity.EntityType entityType, XElement functionMappingElement)
        {
            var cudFunctionMapping = new CUDFunctionMapping();
            var ssdlFunction = ssdlContainer.Functions.GetByName(GetName(functionMappingElement.Attribute("FunctionName").Value));
            cudFunctionMapping.SSDLFunction = ssdlFunction;
            SetCUDFunctionParametersMapping(entityType, functionMappingElement, cudFunctionMapping.ParametersMapping, ssdlFunction);
            foreach (var scalarPropertyElement in functionMappingElement.Elements(XName.Get("ResultBinding", MSL_NS)))
                cudFunctionMapping.ResultsMapping[entityType.AllScalarProperties.GetByName(scalarPropertyElement.Attribute("Name").Value)] = scalarPropertyElement.Attribute("ColumnName").Value;
            foreach (var associationEndElement in functionMappingElement.Elements(XName.Get("AssociationEnd", MSL_NS)))
            {
                var navigationProperty = entityType.NavigationProperties.First(np => np.Association.AssociationSetName == associationEndElement.Attribute("AssociationSet").Value);
                var cudFunctionAssociationMapping = new CUDFunctionAssociationMapping { Association = navigationProperty.Association, FromRole = associationEndElement.Attribute("From").Value, ToRole = associationEndElement.Attribute("To").Value };
                cudFunctionMapping.AssociationMappings.Add(cudFunctionAssociationMapping);
                SetCUDFunctionParametersScalarMapping(navigationProperty.RelatedEntityType, associationEndElement, cudFunctionAssociationMapping.AssociationPropertiesMapping, ssdlFunction);
            }
            return cudFunctionMapping;
        }

        private static void SetCUDFunctionParametersMapping(EDMDesigner.Business.CSDL.Type.TypeBase entityType, XElement functionMappingElement, EntityTypeCUDFunctionParametersMapping cudFunctionParametersMapping, Function ssdlFunction)
        {
            SetCUDFunctionParametersScalarMapping(entityType, functionMappingElement, cudFunctionParametersMapping, ssdlFunction);
            foreach (var complexPropertyElement in functionMappingElement.Elements(XName.Get("ComplexProperty", MSL_NS)))
            {
                var cudFunctionComplexPropertyParametersMapping = new EntityTypeCUDFunctionParametersMapping();
                var complexProperty = entityType.AllComplexProperties.GetByName(complexPropertyElement.Attribute("Name").Value);
                cudFunctionParametersMapping.ComplexPropertiesMapping.Add(new KeyValuePair<ComplexProperty, EntityTypeCUDFunctionParametersMapping>(complexProperty, cudFunctionComplexPropertyParametersMapping));
                SetCUDFunctionParametersMapping(complexProperty.ComplexType, complexPropertyElement, cudFunctionComplexPropertyParametersMapping, ssdlFunction);
            }
        }

        private static void SetCUDFunctionParametersScalarMapping(EDMDesigner.Business.CSDL.Type.TypeBase entityType, XElement functionMappingElement, CUDFunctionParametersMapping cudFunctionParametersMapping, Function ssdlFunction)
        {
            foreach (var scalarPropertyElement in functionMappingElement.Elements(XName.Get("ScalarProperty", MSL_NS)))
            {
                var scalarProperty = entityType.AllScalarProperties.GetByName(scalarPropertyElement.Attribute("Name").Value);
                var parameter = ssdlFunction.Parameters.GetByName(scalarPropertyElement.Attribute("ParameterName").Value);
                var versionAttribute = scalarPropertyElement.Attribute("Version");
                var functionParameterMapping = new FunctionParameterMapping { SSDLFunctionParameter = parameter };
                if (versionAttribute != null)
                    functionParameterMapping.Version = versionAttribute.Value.ToLower() == "current" ? FunctionParameterVersion.Current : FunctionParameterVersion.Original;
                cudFunctionParametersMapping[scalarProperty] = functionParameterMapping;
            }
        }

        private static void MappComplexProperties(XElement complexMappingParentElement, MappingBase complexMappingOwner, TypeBase type, EDMDesigner.Business.SSDL.EntityType.EntityType table)
        {
            foreach (var complexPropertyElement in complexMappingParentElement.Elements(XName.Get("ComplexProperty", MSL_NS)))
            {
                var complexProperty = type.AllComplexProperties.GetByName(complexPropertyElement.Attribute("Name").Value);
                var complexPropertyMapping = complexMappingOwner[complexProperty];
                foreach (var scalarPropertyElement in complexPropertyElement.Elements(XName.Get("ScalarProperty", MSL_NS)))
                {
                    var scalarProperty = complexProperty.ComplexType.AllScalarProperties.GetByName(scalarPropertyElement.Attribute("Name").Value);
                    var column = table.Properties.First(c => c.Name == scalarPropertyElement.Attribute("ColumnName").Value);
                    complexPropertyMapping[scalarProperty, table] = column;
                }
                MappComplexProperties(complexPropertyElement, complexPropertyMapping, complexProperty.ComplexType, table);
            }
        }

        private static void SetStringValueFromAttribute(XElement element, string attributeName, Action<string> setAction)
        {
            SetStringValueFromAttribute(element, attributeName, "", setAction);
        }
        private static void SetStringValueFromAttribute(XElement element, string attributeName, string ns, Action<string> setAction)
        {
            var attribute = element.Attribute(XName.Get(attributeName, ns));
            if (attribute != null)
                setAction(attribute.Value);
        }
        private static void SetBoolValueFromAttribute(XElement element, string attributeName, Action<bool> setAction)
        {
            SetStringValueFromAttribute(element, attributeName,
                value =>
                {
                    switch (value)
                    {
                        case "0":
                        case "false":
                        case "False":
                            setAction(false);
                            break;
                        case "1":
                        case "true":
                        case "True":
                            setAction(true);
                            break;
                        default:
                            throw new NotImplementedException();
                    }
                });
        }
        private static void SetIntValueFromAttribute(XElement element, string attributeName, Action<int> setAction)
        {
            SetStringValueFromAttribute(element, attributeName,
                value =>
                {
                    if (value != "Max")
                        setAction(int.Parse(value));
                });
        }
        private static void SetEnumValueFromAttribute<T>(XElement element, string attribute, Action<T> setAction)
        {
            SetEnumValueFromAttribute<T>(element, attribute, "", setAction);
        }
        private static void SetEnumValueFromAttribute<T>(XElement element, string attribute, string ns, Action<T> setAction)
        {
            SetStringValueFromAttribute(element, attribute, ns,
                enumName =>
                {
                    T[] values = Enum.GetValues(typeof(T)).Cast<object>().Where(v => v.ToString() == enumName).Select(v => (T)v).Take(1).ToArray();
                    if (values.Length == 0)
                        throw new NotImplementedException();
                    setAction(values[0]);
                });
        }
        private static void SetCardinalityValueFromAttribute(XElement element, Action<Cardinality> setAction)
        {
            SetStringValueFromAttribute(element, "Multiplicity", multiplicity => setAction(EDMDesigner.Business.AssociationBase.CardinalityStringConverter.CardinalityFromString(multiplicity)));
        }
        private static void SetVisibilityValueFromAttribute(XElement element, string attribute, Action<Visibility> setAction)
        {
            SetEnumValueFromAttribute<Visibility>(element, attribute, CSDL_VIBILITY_NS, setAction);
        }

        private static void SetStringValueFromElement(XElement element, string elementName, string ns, Action<string> setAction)
        {
            var subElement = element.Element(XName.Get(elementName, ns));
            if (subElement != null)
                setAction(subElement.Value);
        }

        private static PropertyType? GetScalarPropertyTypeFromAttribute(XElement element)
        {
            return GetScalarPropertyTypeFromAttribute(element.Attribute("Type").Value);
        }
        private static PropertyType? GetScalarPropertyTypeFromAttribute(string propertyTypeValue)
        {
            var values = Enum.GetValues(typeof(PropertyType)).Cast<object>().Where(v => v.ToString() == propertyTypeValue).Select(v => (PropertyType)v).Take(1).ToArray();
            if (values.Length == 0)
                return null;
            return values[0];
        }

        private static string GetName(string fullName)
        {
            return fullName.Substring(fullName.LastIndexOf(".") + 1);
        }
    }
}

